#ifndef __EPSILON_3D__VCACHE_H__
#define __EPSILON_3D__VCACHE_H__

#include "resource.h"

#include "string.h" // memcpy

#include "driver.h"

#include "../types.h"

struct e3dVertexOLD {
  float pos    [3];
//  float normal [3];
  byte  color  [4];
  float st     [2];
};

struct e3dVertex {
  float pos     [3];
  float normal  [3];
  float tangent [3];
  byte  color   [4];
  float st      [2];
};


class e3dIndexCache;


class e3dVertexBuffer
{
public:
  e3dVertexBuffer (void) {
  }

  e3dVertexBuffer (e3dVertex* vertices, int num)
  {
    if (num != 0) {
      verts.resize (num);
      memcpy (&verts [0], vertices, sizeof (e3dVertex) * num);
    }
  }

  e3dVertexBuffer (const e3dVertexBuffer* buffer)
  {
    copy (*buffer);
  }

  virtual ~e3dVertexBuffer (void)
  {
    destroy ();
  }

  virtual void destroy (void);
  virtual void copy    (const e3dVertexBuffer& buffer);

  virtual size_t size (void) const
  {
    return verts.size ();
  }

  virtual void resize (size_t size) = 0;

  virtual void set_vtx (unsigned int idx, e3dVertex data)
  {
    if (idx > verts.size ()) {
      verts.resize (idx);
    }

    verts [idx] = data;

    update (idx, 1);
  }

  virtual e3dVertex get_vtx (unsigned int idx) const
  {
    return verts [idx];
  }

 /* Notify the implementation that the contents have changed...
  
     -:- Required for GPU cached vertex buffers.

       Specifies the index of the first vertex changed, and the number of
         vertices (sequentially) changed from that index.
  */
  virtual void update  (unsigned int first, unsigned int count) = 0;

  virtual void bind    (void)    = 0;
  virtual void unbind  (void)    = 0;
  virtual void enable  (void)    = 0;
  virtual void disable (void)    = 0;
  virtual void draw    (e3dRenderStates::PrimitiveType primitive, unsigned int count)                     = 0;
  virtual void draw    (e3dRenderStates::PrimitiveType primitive, int          first, unsigned int count) = 0;

  virtual void draw_indexed (e3dRenderStates::PrimitiveType primitive, e3dIndexCache* indices, int first, unsigned int count) = 0;

  virtual size_t msize (void) const
  {
    size_t size = verts.size () * sizeof (e3dVertex);
    return size;
  }

protected:
  std::vector<e3dVertex> verts;
};

#include "OpenGL.h"

namespace e3dOpenGL
{
  class VertexBuffer : public e3dVertexBuffer, public e3dResourceGPU
  {
  public:
    VertexBuffer (void) {
      GPU_size_ = 0;
      handle_   = 0;
      update (0, verts.size ());
    }

    VertexBuffer (e3dVertex* vertices, int num)
    {
      GPU_size_ = 0;
      handle_   = 0;
      update (0, verts.size ());
    }

    VertexBuffer (const e3dVertexBuffer* buffer)
    {
      GPU_size_ = 0;
      copy   (*buffer);
      update (0, verts.size ());
    }

    virtual ~VertexBuffer (void)
    {
      destroy ();
    }

    void destroy (void);

    void update (unsigned int first, unsigned int count);

    void resize  (size_t size);

    void bind   (void);
    void unbind (void);

    void enable  (void);
    void disable (void);

    void draw (e3dRenderStates::PrimitiveType, unsigned int count);
    void draw (e3dRenderStates::PrimitiveType, int          first, unsigned int count);

    void draw_indexed (e3dRenderStates::PrimitiveType primitive, e3dIndexCache* indices, int first, unsigned int count);

  private:
    GLuint handle_;
    GLuint GPU_size_;
  };
};

#ifdef HAS_D3D
#include <d3d9.h>
#include <d3dx9.h>

#define D3DFVF_VERTEX_BUFFER (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

namespace e3dDirect3D {
  class VertexBuffer : public e3dVertexBuffer, public e3dResourceGPU
  {
  public:
    VertexBuffer (void) {
      _vb         = NULL;
    }

    VertexBuffer (e3dVertex* vertices, int num)
    {
      _vb = NULL;
    }

    VertexBuffer (const e3dVertexBuffer* buffer)
    {
      copy (*buffer);
    }

    virtual ~VertexBuffer (void)
    {
      destroy ();
    }

    void destroy (void);

    void resize (size_t size);

    void bind   (void);
    void unbind (void);

    void enable  (void);
    void disable (void);

    void draw (e3dRenderStates::PrimitiveType, unsigned int count);
    void draw (e3dRenderStates::PrimitiveType, int          first, unsigned int count);

    void draw_indexed (e3dRenderStates::PrimitiveType primitive, e3dIndexCache* indices, int first, unsigned int count);

  protected:
    LPDIRECT3DVERTEXBUFFER9 _vb;
  };
};
#endif

class e3dVertexCache : public e3dResource
{
public:
           e3dVertexCache (e3dVertexBuffer* buf);
  virtual ~e3dVertexCache (void)
  {
    if (_buffer != NULL) {
      delete _buffer;
      _buffer = NULL;
    }
  }

  void Resize (size_t size)
  {
    if (! _buffer) {
      return;
    }

    _buffer->resize (size);
  }

  size_t GetSize (void)
  {
    if (! _buffer) {
      return 0;
    }

    return _buffer->size ();
  }

  void SetVertex (unsigned int idx, e3dVertex data)
  {
    if (! _buffer) {
      return;
    }

    _buffer->set_vtx (idx, data);
  }

  e3dVertex GetVertex (unsigned int idx)
  {
    if (! _buffer) {
      e3dVertex identity;
      return identity;
    }

    return _buffer->get_vtx (idx);
  }

  void Bind (void)
  {
    if (! _buffer) {
      return;
    }

    _buffer->bind ();
  }

  void Unbind (void) {
    if (! _buffer) {
      return;
    }

    _buffer->unbind ();
  }

  void Enable (void)
  {
    if (! _buffer) {
      return;
    }

    _buffer->enable ();
  }

  void Disable (void)
  {
    if (! _buffer) {
      return;
    }

    _buffer->disable ();
  }

  void Draw (e3dRenderStates::PrimitiveType primitive, unsigned int count)
  {
    if (! _buffer) {
      return;
    }

    _buffer->draw (primitive, count);
  }
  
  void Draw (e3dRenderStates::PrimitiveType primitive, int first, unsigned int count)
  {
    if (! _buffer) {
      return;
    }

    _buffer->draw (primitive, first, count);
  }

  void DrawIndexed (e3dRenderStates::PrimitiveType primitive, e3dIndexCache* indices, int first, unsigned int count)
  {
    if (! _buffer) {
      return;
    }

    if (! indices) {
      return;
    }

    _buffer->draw_indexed (primitive, indices, first, count);
  }

  size_t MemSize (void)
  {
    size_t _size = sizeof (e3dVertexCache);

    if (_buffer)
      _size += _buffer->msize ();

    return _size;
  }

public:
  e3dVertexBuffer* _buffer;
};



typedef short e3dIndex; // D3DFMT_INDEX16

class e3dIndexBuffer
{
public:
  e3dIndexBuffer (void) {
  }

  e3dIndexBuffer (e3dIndex* indices, int num)
  {
    if (num != 0) {
      this->indices.resize (num);
      memcpy (&this->indices [0], indices, sizeof (e3dIndex) * num);
    }
  }

  e3dIndexBuffer (const e3dIndexBuffer* buffer)
  {
    copy (*buffer);
  }

  virtual ~e3dIndexBuffer (void)
  {
    destroy ();
  }

  virtual void destroy (void);
  virtual void copy    (const e3dIndexBuffer& buffer);

  virtual size_t size (void) const
  {
    return indices.size ();
  }

  virtual void resize (size_t size) = 0;

  virtual void set_idx (unsigned int idx, e3dIndex index)
  {
    if (idx > indices.size ()) {
      indices.resize (idx);
    }

    indices [idx] = index;

    update (idx, 1);
  }

  virtual e3dIndex get_idx (unsigned int idx)
  {
    return indices [idx];
  }

  virtual void* get_idx_ptr (void)
  {
    return &indices [0];
  }

 /* Notify the implementation that the contents have changed...
  
     -:- Required for GPU cached vertex buffers.

       Specifies the index of the first vertex changed, and the number of
         vertices (sequentially) changed from that index.
  */
  virtual void update  (unsigned int first, unsigned int count) = 0;

  virtual void bind    (void)    = 0;
  virtual void unbind  (void)    = 0;
  virtual void enable  (void)    = 0;
  virtual void disable (void)    = 0;

  virtual size_t msize (void) const
  {
    size_t size = indices.size () * sizeof (e3dIndex);
    return size;
  }

protected:
  std::vector<e3dIndex> indices;
};

namespace e3dOpenGL {
  class IndexBuffer : public e3dIndexBuffer, public e3dResourceGPU
  {
  public:
    IndexBuffer (void) {
      GPU_size_ = 0;
      handle_   = 0;
      update (0, indices.size ());
    }

    IndexBuffer (const e3dIndexBuffer* buffer)
    {
      copy (*buffer);

      GPU_size_ = 0;
      handle_   = 0;
      update (0, indices.size ());
    }

    virtual ~IndexBuffer (void)
    {
      //destroy ();
    }

    void destroy (void);

    void resize (size_t size);

    void update (unsigned int first, unsigned int count);

    /* OpenGL uses Index Buffer Objects, which are bound to
         the ELEMENT_ARRAY as a buffer object, and the beginning
           address is at 0. */
    void* get_idx_ptr (void) { return (void *)NULL; }

    void bind   (void);
    void unbind (void);

    void enable  (void);
    void disable (void);

  private:
    GLuint handle_;
    GLuint GPU_size_;
  };
}

#ifdef HAS_D3D
namespace e3dDirect3D {
  class IndexBuffer : public e3dIndexBuffer, public e3dResourceGPU
  {
  public:
    IndexBuffer (void) {
      _ib         = NULL;
    }

    IndexBuffer (e3dIndex* indices, int num)
    {
      _ib = NULL;
    }

    IndexBuffer (const e3dIndexBuffer* buffer)
    {
      copy (*buffer);
    }

    virtual ~IndexBuffer (void)
    {
      //destroy ();
    }

    void destroy (void);

    void resize (size_t size);

    void bind (void);

    void enable  (void);
    void disable (void);

  private:
    LPDIRECT3DINDEXBUFFER9 _ib;
  };
}
#endif

class e3dIndexCache : public e3dResource
{
public:
           e3dIndexCache (e3dIndexBuffer* buf);
  virtual ~e3dIndexCache (void)
  {
    if (_buffer != NULL) {
      delete _buffer;
      _buffer = NULL;
    }
  }

  void Resize (size_t size)
  {
    if (! _buffer) {
      return;
    }

    _buffer->resize (size);
  }

  size_t GetSize (void)
  {
    if (! _buffer) {
      return 0;
    }

    return _buffer->size ();
  }

  void SetIndex (unsigned int idx, e3dIndex index)
  {
    if (! _buffer) {
      return;
    }

    _buffer->set_idx (idx, index);
  }

  e3dIndex GetIndex (unsigned int idx)
  {
    if (! _buffer) {
      e3dIndex identity (0);
      return identity;
    }

    return _buffer->get_idx (idx);
  }

  void* GetIndexPointer (void)
  {
    if (! _buffer) {
      return NULL;
    }

    return _buffer->get_idx_ptr ();
  }

  void Bind (void)
  {
    if (! _buffer) {
      return;
    }

    _buffer->bind ();
  }

  void Unbind (void)
  {
    if (! _buffer) {
      return;
    }

    _buffer->unbind ();
  }

  void Enable (void)
  {
    if (! _buffer) {
      return;
    }

    _buffer->enable ();
  }

  void Disable (void)
  {
    if (! _buffer) {
      return;
    }

    _buffer->disable ();
  }

  size_t MemSize (void)
  {
    size_t size = sizeof (e3dIndexCache);

    if (_buffer) {
      size += _buffer->msize ();
    }

    return size;
  }

  e3dIndexBuffer* _buffer;
};

#endif /* __EPSILON_3D__VCACHE_H__ */
